#include <bits/stdc++.h>
using namespace std;

/*
909. 蛇梯棋
已解答
中等
相关标签
premium lock icon
相关企业
给你一个大小为 n x n 的整数矩阵 board ，方格按从 1 到 n2 编号，编号遵循 转行交替方式 ，从左下角开始 （即，从 board[n - 1][0] 开始）的每一行改变方向。

你一开始位于棋盘上的方格  1。每一回合，玩家需要从当前方格 curr 开始出发，按下述要求前进：

选定目标方格 next ，目标方格的编号在范围 [curr + 1, min(curr + 6, n2)] 。
该选择模拟了掷 六面体骰子 的情景，无论棋盘大小如何，玩家最多只能有 6 个目的地。
传送玩家：如果目标方格 next 处存在蛇或梯子，那么玩家会传送到蛇或梯子的目的地。否则，玩家传送到目标方格 next 。 
当玩家到达编号 n2 的方格时，游戏结束。
如果 board[r][c] != -1 ，位于 r 行 c 列的棋盘格中可能存在 “蛇” 或 “梯子”。那个蛇或梯子的目的地将会是 board[r][c]。编号为 1 和 n2 的方格不是任何蛇或梯子的起点。

注意，玩家在每次掷骰的前进过程中最多只能爬过蛇或梯子一次：就算目的地是另一条蛇或梯子的起点，玩家也 不能 继续移动。

举个例子，假设棋盘是 [[-1,4],[-1,3]] ，第一次移动，玩家的目标方格是 2 。那么这个玩家将会顺着梯子到达方格 3 ，但 不能 顺着方格 3 上的梯子前往方格 4 。（简单来说，类似飞行棋，玩家掷出骰子点数后移动对应格数，遇到单向的路径（即梯子或蛇）可以直接跳到路径的终点，但如果多个路径首尾相连，也不能连续跳多个路径）
返回达到编号为 n2 的方格所需的最少掷骰次数，如果不可能，则返回 -1。

 

示例 1：


输入：board = [[-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1],[-1,35,-1,-1,13,-1],[-1,-1,-1,-1,-1,-1],[-1,15,-1,-1,-1,-1]]
输出：4
解释：
首先，从方格 1 [第 5 行，第 0 列] 开始。 
先决定移动到方格 2 ，并必须爬过梯子移动到到方格 15 。
然后决定移动到方格 17 [第 3 行，第 4 列]，必须爬过蛇到方格 13 。
接着决定移动到方格 14 ，且必须通过梯子移动到方格 35 。 
最后决定移动到方格 36 , 游戏结束。 
可以证明需要至少 4 次移动才能到达最后一个方格，所以答案是 4 。 
示例 2：

输入：board = [[-1,-1],[-1,3]]
输出：1
 

提示：

n == board.length == board[i].length
2 <= n <= 20
board[i][j] 的值是 -1 或在范围 [1, n2] 内
编号为 1 和 n2 的方格上没有蛇或梯子
*/

// 法一
class Solution {
public:
    int snakesAndLadders(vector<vector<int>>& board) {
        int n = board.size();
        vector<int> visited(n * n + 1, 0);   // 记录是否访问过，避免重复处理
        queue<pair<int, int>> q;             // BFS队列，存储{当前位置, 步数}
        q.push({1, 0});                      // 初始位置为1，步数为0
        visited[1] = 1;                      // 标记初始位置已访问

        // BFS主循环
        while (!q.empty()) {
            auto cur = q.front();
            q.pop();
            int pos = cur.first;             // 当前棋盘编号
            int steps = cur.second;          // 当前步数

            // 到达终点，返回步数
            if (pos == n * n) {
                return steps;
            }

            // 模拟掷骰子1~6
            for (int i = 1; i <= 6; ++i) {
                int next_pos = pos + i;      // 下一步的编号
                if (next_pos > n * n) break;  // 超出棋盘范围则跳过

                // 将编号转换为二维坐标（行、列）
                int row = (next_pos - 1) / n; // 计算行号（从0开始）
                int col = (next_pos - 1) % n; // 计算列号（从0开始）
                
                // 处理Z字形编号：奇数行反转列方向
                if (row % 2 == 1) {
                    col = n - 1 - col;
                }
                row = n - 1 - row;           // 棋盘从下往上编号，需反转行

                // 如果当前位置是蛇或梯子，则跳转到目标位置
                if (board[row][col] != -1) {
                    next_pos = board[row][col];
                }

                // 如果目标位置未访问过，加入队列并标记
                if (!visited[next_pos]) {
                    visited[next_pos] = 1;
                    q.push({next_pos, steps + 1});
                }
            }
        }
        return -1;  // 无法到达终点
    }
};

/* 今天第一次就直接最优 没有尝试新方法了 */
// 换一种写法吧
class Solution {
public:
    int snakesAndLadders(vector<vector<int>>& board) {
        int len = board.size();
        // 预计算每个编号对应的二维坐标（行、列）
        vector<pair<int, int>> coord(len * len + 1);  // coord[1]~coord[len*len]存储坐标
        for(int i = 0; i < len * len; ++i) {
            // 计算原始行列（从左上角开始编号）
            int x1 = i / len;  // 原始行号（从0开始）
            int y1 = i % len;  // 原始列号（从0开始）
            // 转换为实际坐标（左下角开始编号，Z字形）
            int x = len - 1 - x1;  // 反转行方向（从下到上）
            int y = (x1 & 1) ? len - 1 - y1 : y1;  // 奇数行反转列方向
            coord[i + 1] = {x, y};  // 编号从1开始
        }

        // BFS初始化
        deque<int> dq;  // 双端队列存储待处理的编号
        dq.push_back(1);  // 起点编号为1
        vector<bool> visited(len * len + 1, false);  // 标记已访问的编号
        int level = 0;  // 记录当前步数
        visited[1] = true;  // 标记起点已访问

        // BFS主循环
        while(!dq.empty()) {
            int len1 = dq.size();  // 当前层的节点数
            // 处理当前层的所有节点
            for(int i = 0; i < len1; ++i) {
                int node = dq.front();
                dq.pop_front();

                // 到达终点，返回步数
                if(node == len * len)
                    return level;

                // 模拟掷骰子1~6步
                for(int j = node + 1; j <= std::min(node + 6, len * len); ++j) {
                    // 获取编号j的二维坐标
                    int x = coord[j].first;
                    int y = coord[j].second;
                    int next = j;
                    // 如果当前位置是蛇或梯子，跳转到目标位置
                    if(board[x][y] != -1)
                        next = board[x][y];

                    // 如果目标位置未访问过，加入队列并标记
                    if(!visited[next]) {
                        visited[next] = true;
                        dq.push_back(next);
                    }
                }
            }
            // 当前层处理完毕，步数+1
            ++level;
        }
        // 无法到达终点
        return -1;
    }
};